package moe.server;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.URLDecoder;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import java.util.logging.Level;
import java.util.logging.Logger;

import moe.config.Configuration;
import moe.config.Constants;
import moe.config.PropertiesUtil;

import org.apache.http.HttpEntity;
import org.apache.http.HttpException;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.MethodNotSupportedException;
import org.apache.http.entity.ContentProducer;
import org.apache.http.entity.EntityTemplate;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpRequestHandler;

public class StaticResourcesHandler implements HttpRequestHandler {

	protected static final Logger log = Logger.getLogger(StaticResourcesHandler.class.getName());

	protected static Map<String, String> mimeTypeMap;
	protected final static Map<String, String> rewriteRule = new LinkedHashMap<String, String>();
	protected final static SimpleDateFormat dateFormat = new SimpleDateFormat(
			"EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
	protected final static String ROOT_DIR = System.getProperty("user.dir") + File.separator
			+ "resources" + File.separator;

	static {
		initMimeType();
		initRewriteRule();
		dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
	}

	private static void initMimeType() {
		try {
			mimeTypeMap = PropertiesUtil.loadPropertiesFile(ClassLoader
					.getSystemResourceAsStream(Constants.MIME_TYPES_FILE));
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	private static void initRewriteRule() {
		String[] keys = PropertiesUtil.getPropertyKeys();
		/*
		 * 处理用户指定的moe.imouto.p{X}路径
		 */
		for (String key : keys) {
			if (key.startsWith("moe.imouto.p")) {
				String replace = PropertiesUtil.getProperty(key);
				if (!replace.endsWith(File.separator)) {
					replace += File.separator;
				}
				replace = replace.replaceAll("\\\\", "\\\\\\\\").replaceAll("\\$", "\\\\\\$");
				// System.out.println(replace);
				key = key.replaceAll("\\.", "\\.");

				if (!key.startsWith("moe.imouto.preview") && Configuration.isImoutoCollectPattern()) {
					rewriteRule.put("^/" + key
							+ "/((?:[a-f]|[A-F]|[0-9])+)/moe (\\d+) .+\\.(\\w{3})$", replace
							+ "moe $2.$1.$3");
				} else {
					rewriteRule.put("^/" + key + "/(.+)$", replace + "$1");
				}
			}
		}

		/*
		 * 处理默认moe.imouto.p{X}路径
		 */
		String defaultPath = Configuration.getDefaultPath();
		if (!defaultPath.endsWith(File.separator)) {
			defaultPath += File.separator;
		}
		if (Configuration.isImoutoCollectPattern()) {
			rewriteRule.put(
					"^/(moe\\.imouto\\.p\\d+)/((?:[a-f]|[A-F]|[0-9])+)/moe (\\d+) .+\\.(\\w{3})$",
					(defaultPath + "$1" + File.separator + "moe $3.$2.$4").replaceAll("\\\\",
							"\\\\\\\\"));
		}
		rewriteRule.put("^/(moe\\.imouto\\.p\\w+)/(.+)$",
				(defaultPath + "$1" + File.separator + "$2").replaceAll("\\\\", "\\\\\\\\"));

		/*
		 * 处理其余的路径
		 */
		rewriteRule.put("^/(.+)$", (ROOT_DIR + "$1").replaceAll("\\\\", "\\\\\\\\"));
		/*
		 * for (String key : rewriteRule.keySet()) { System.out.println(key + " : " + rewriteRule.get(key)); }
		 */
	}

	public void handle(HttpRequest request, HttpResponse response, HttpContext context)
			throws HttpException, IOException {
		String method = request.getRequestLine().getMethod().toUpperCase(Locale.ENGLISH);
		if (!method.equals("GET")) {
			throw new MethodNotSupportedException(method + " method not supported");
		}
		String requestURI = request.getRequestLine().getUri();
		int index = requestURI.indexOf("?");
		if (index != -1) {
			requestURI = requestURI.substring(0, index);
		}
		String filePath = resolveFilePath(URLDecoder.decode(requestURI, "UTF-8"));
		if (filePath == null || filePath.equals("")) {
			filePath = requestURI;
		}
		final File file = new File(filePath);
		if (!file.exists()) {
			fileNotFound(response, requestURI);
		} else if (!file.canRead() || file.isDirectory()) {
			accessDenied(response, file);
		} else {
			sendFile(request, response, context, file);
		}
	}

	protected void fileNotFound(HttpResponse response, final String path) {
		response.setStatusCode(HttpStatus.SC_NOT_FOUND);
		EntityTemplate body = new EntityTemplate(new ContentProducer() {

			public void writeTo(final OutputStream outstream) throws IOException {
				OutputStreamWriter writer = new OutputStreamWriter(outstream, "UTF-8");
				writer.write("<html><body><h1>");
				writer.write("File ");
				writer.write(path);
				writer.write(" not found");
				writer.write("</h1></body></html>");
				writer.flush();
			}
		});
		body.setContentType("text/html; charset=UTF-8");
		response.setEntity(body);
		log.warning("File not found: " + path);
	}

	protected void accessDenied(HttpResponse response, File file) {
		response.setStatusCode(HttpStatus.SC_FORBIDDEN);
		EntityTemplate body = new EntityTemplate(new ContentProducer() {

			public void writeTo(final OutputStream outstream) throws IOException {
				OutputStreamWriter writer = new OutputStreamWriter(outstream, "UTF-8");
				writer.write("<html><body><h1>");
				writer.write("Access denied");
				writer.write("</h1></body></html>");
				writer.flush();
			}

		});
		body.setContentType("text/html; charset=UTF-8");
		response.setEntity(body);
		log.warning("Cannot load the file: " + file.getPath());
	}

	protected String resolveFilePath(String uri) {
		String result = null;
		if (uri.equals("/")) {
			result = ROOT_DIR + "index.html";
		} else if (uri.endsWith("/")) {
			result = ROOT_DIR;
		}
		if (result == null) {
			result = applyRewriteRule(uri);
		}
		if (log.isLoggable(Level.FINER)) {
			log.finer("URI: \"" + uri + "\" mapped to: \"" + result + "\"");
		}
		return result;
	}

	protected String applyRewriteRule(String uri) {
		String result = null;
		try {
			for (String regex : rewriteRule.keySet()) {
				if (uri.matches(regex)) {
					// System.out.println(regex + " | " + rewriteRule.get(regex));
					result = uri.replaceAll(regex, rewriteRule.get(regex));
					break;
				}
			}
			if (result != null && File.separator.equals("\\")) {
				result = result.replaceAll("/", "\\\\");
			}
		} catch (Exception e) {
			if (log.isLoggable(Level.SEVERE)) {
				log.severe("URI(" + uri + ") mapping error: " + e.getMessage());
			}
			throw new RuntimeException(e);
		}
		return result;
	}

	protected void sendFile(HttpRequest request, HttpResponse response, HttpContext context,
			File file) {
		String lastModified = dateFormat.format(new Date(file.lastModified()));
		if (request.containsHeader(HttpHeaders.IF_MODIFIED_SINCE)
				&& request.getFirstHeader(HttpHeaders.IF_MODIFIED_SINCE) != null
				&& lastModified.equals(request.getFirstHeader(HttpHeaders.IF_MODIFIED_SINCE)
						.getValue())) {
			response.setStatusCode(HttpStatus.SC_NOT_MODIFIED);
		} else {
			response.setStatusCode(HttpStatus.SC_OK);
			response.addHeader(HttpHeaders.LAST_MODIFIED, lastModified);
			response.addHeader(HttpHeaders.CACHE_CONTROL, "private, max-age=3600");

			HttpEntity entity = createHttpEntity(request, response, context, file);
			response.setEntity(entity);
			if (log.isLoggable(Level.FINER)) {
				log.finer("Serving file " + file.getPath());
			}
		}
	}

	protected HttpEntity createHttpEntity(HttpRequest request, HttpResponse response,
			HttpContext context, File file) {
		String extension = file.getName().substring(file.getName().lastIndexOf('.') + 1);
		String mimeType = mimeTypeMap.get(extension.toLowerCase());
		if (mimeType == null) {
			mimeType = "application/octet-stream";
		}
		FileChannelEntity entity = new FileChannelEntity(file, mimeType);
		return entity;
	}
}
